package ar.uba.fi.celdas.bc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ar.fi.uba.celdas.correccion.planificacion.CorrectorPlanificacion;
import ar.fi.uba.celdas.correccion.planificacion.HeuristicaPlanificacionType;
import ar.fi.uba.celdas.correccion.prediccion.CorrectorPrediccion;
import ar.fi.uba.celdas.interfaces.ambiente.IAccion;
import ar.fi.uba.celdas.interfaces.ambiente.IInfoRobot;
import ar.fi.uba.celdas.interfaces.bc.IBaseConocimientos;
import ar.fi.uba.celdas.interfaces.bc.Regla;
import ar.fi.uba.celdas.interfaces.bc.Sensor;
import ar.fi.uba.celdas.interfaces.planificacion.IPlanificacion;
import ar.fi.uba.celdas.interfaces.ponderador.IPonderador;
import ar.uba.fi.celdas.util.AccionFactory;

public class BaseConocimientos implements IBaseConocimientos {

	private static final Log log = LogFactory.getLog(BaseConocimientos.class);

	private List<Regla> baseConocimiento = new LinkedList<Regla>();
	private Collection<Sensor> objetivo;
	private Collection<Sensor> estadoAnterior = null;
	private Collection<Sensor> estadoActual = null;

	private Regla ultimaReglaEjecutada = null;

	private IPlanificacion planificador = null;
	private IPonderador ponderador = null;

	private List<Regla> reglasPlan = null;
	private int indiceReglaPlanEjecutada = 0;

	@Override
	public List<Regla> getReglas() {

		return baseConocimiento;
	}

	@Override
	public Collection<Sensor> getObjetivo() {

		return objetivo;
	}

	@Override
	public Collection<Sensor> getEstadoAnterior() {

		return estadoAnterior;
	}

	@Override
	public Collection<Sensor> getEstadoActual() {

		return estadoActual;
	}

	@Override
	public Collection<IAccion> getAccionesDisponibles() {

		List<Integer> valores = new ArrayList<Integer>();
		Regla regla = new Regla();
		Iterator<Regla> it = baseConocimiento.iterator();
		regla = it.next();

		while (it.hasNext()) {

			if (it.next().getUtilidad() > regla.getUtilidad()) {

				regla = it.next();
			}
		}
		return regla.getAcciones();
	}

	@Override
	public List<IAccion> procesarSiguientesAcciones(IInfoRobot infoRobot) {

		List<IAccion> accionesAEjecutar = new ArrayList<IAccion>();

		// Actualiza los estados
		estadoAnterior = estadoActual;
		estadoActual = infoRobot.getEstado();
		log.trace("Actualizando estados...");
		log.debug("Estado anterior: " + estadoAnterior);

		Regla reglaLocal = null;
		// Si es la primera ejecución
		if (baseConocimiento.isEmpty() && estadoAnterior == null) {

			log.trace("Primera ejecución");
			log.debug("No hay datos de estados anteriores ni reglas: esta es la primera ejecución");

			// Ejecuto una acción por defecto
			log.trace("Creando acción por defecto");
			// TODO: Usar Strategy para decidir cómo elegir la primera acción
			IAccion accionAvanzar = AccionFactory.crearAccion("AVANZAR");
			accionesAEjecutar.add(accionAvanzar);

			// Crea una pseudo-regla local para arrancar
			log.trace("Creando pseudo-regla para arrancar");
			reglaLocal = crearReglaLocal(estadoActual, estadoActual,
					accionesAEjecutar);
			log.debug(reglaLocal);
		} else {

			log.trace("Esta no es la primera ejecución");

			if (!infoRobot.ultimasAccionesExitosas()) {

				CorrectorPlanificacion.getCorrector(
						HeuristicaPlanificacionType.RETRACCION).corregirRegla(
						ultimaReglaEjecutada, this);
				reglasPlan = null;
			} else {

				if (!ultimaReglaEjecutada.match(estadoActual)) {

					CorrectorPrediccion.getCorrector().corregirRegla(
							ultimaReglaEjecutada, this);
					reglasPlan = null;
				} else {

					log.trace("Creando regla local");
					reglaLocal = crearReglaLocal(estadoAnterior, estadoActual,
							ultimaReglaEjecutada.getAcciones());
					reglaLocal.registrarUsoExitoso();
					reglaLocal.setUtilidad(1);
					log.debug(reglaLocal);
				}
			}
		}

		if (reglasPlan == null || reglasPlan.isEmpty()
				|| indiceReglaPlanEjecutada >= reglasPlan.size()) {

			log.trace("La regla local no está registrada");
			// No hay plan de acción, hay que crear uno usando el planificador
			// Habiendo incorporado antes la regla local para que pueda
			// planificar
			log.trace("Agregando regla local");
			baseConocimiento.add(reglaLocal);

			log.trace("Llamando al planificador");
			reglasPlan = planificador.planificar(this);
			indiceReglaPlanEjecutada = 0;
			log.debug("Reglas del planificador: " + reglasPlan);

			log.trace("Llamando al ponderador");
			ponderador.ponderar(reglasPlan, this);

		}

		ultimaReglaEjecutada = reglasPlan.get(indiceReglaPlanEjecutada++);

		return ultimaReglaEjecutada.getAcciones();
	}

	private Regla crearReglaLocal(Collection<Sensor> condiciones,
			Collection<Sensor> predicciones, List<IAccion> acciones) {

		Regla regla = new Regla(condiciones, predicciones, acciones);

		return regla;
	}

	/**
	 * Busca una regla que concuerde en la base de conocimientos
	 * 
	 * @param estado
	 *            El conjunto de condiciones actuales para buscar una regla
	 * @return La primer regla que concuerde con las condiciones actuales o
	 *         <code>null</code> si no hay reglas que concuerden
	 */
	private Regla encontrarRegla(Regla reglaBuscada) {

		Regla reglaEncontrada = null;

		Iterator<Regla> iteradorReglas = baseConocimiento.iterator();

		while (iteradorReglas.hasNext() && reglaEncontrada == null) {

			Regla regla = iteradorReglas.next();

			if (regla.match(reglaBuscada)) {

				reglaEncontrada = regla;
			}
		}

		return reglaEncontrada;
	}

	@Override
	public int getMaxValor(Sensor sensor) {
		return 0;
	}

	@Override
	public int getMinValor(Sensor sensor) {
		return 0;
	}

	@Override
	public Collection<Sensor> getEstadosConocidos() {
		// TODO Auto-generated method stub
		return null;
	}

	public void setPlanificador(IPlanificacion planificador) {

		this.planificador = planificador;
	}

	public void setPonderador(IPonderador ponderador) {

		this.ponderador = ponderador;
	}
}
